Use SignalR authentication refresh for Blazor Identity - #68663
Conversation
There was a problem hiding this comment.
Pull request overview
Prototype wiring for Blazor Server + Identity to rely on SignalR authentication refresh (and a 30-minute client-driven refresh loop) instead of the template’s IdentityRevalidatingAuthenticationStateProvider, with accompanying tests and template baseline updates.
Changes:
- SignalR TS client: coalesce concurrent
refreshAuthentication()calls and ensure callbacks can trigger follow-up refreshes. - Blazor Web.JS: add a 30-minute periodic refresh loop in
CircuitManager, plus tests and E2E coverage for authenticated→anonymous transition without reconnect. - Blazor Identity template: remove
IdentityRevalidatingAuthenticationStateProviderand its registration/baseline entries.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts | Adds coverage for refresh coalescing and nested refresh scenarios. |
| src/SignalR/clients/ts/signalr/src/HubConnection.ts | Implements coalescing of concurrent refresh operations and completion bookkeeping. |
| src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts | Adds a fixed-interval (30 min) authentication refresh timer and rearm/cleanup logic. |
| src/Components/Web.JS/test/Platform/Circuits/CircuitManagerAuthenticationRefresh.test.ts | Adds unit tests for refresh configuration ordering and 30-minute refresh cadence. |
| src/Components/test/testassets/Components.TestServer/Pages/_ServerHost.cshtml | Adds query-driven hooks to capture/accelerate auth refresh behavior for E2E. |
| src/Components/test/E2ETest/ServerExecutionTests/ServerAuthTest.cs | Adds E2E test for automatic refresh updating circuit auth state without reconnect. |
| src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/* | Removes template provider and DI registration related to identity revalidation. |
| src/ProjectTemplates/test/Templates.Tests/template-baselines.json | Updates template baselines to reflect removed provider file. |
Suppressed comments (1)
src/Components/Web.JS/test/Platform/Circuits/CircuitManagerAuthenticationRefresh.test.ts:76
- The mocked HubConnection used in this test doesn't implement
stop(), butCircuitManager.dispose()callsthis._connection?.stop(). Add astopstub so the test doesn't fail withTypeError: connection.stop is not a function.
const connection = {
on: jest.fn(),
onclose: jest.fn(),
start: () => Promise.resolve(),
state: HubConnectionState.Connected,
refreshAuthentication,
} as unknown as HubConnection;
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| .withAuthenticationRefresh({ | ||
| onAuthenticationRefreshed: context => this.scheduleAuthenticationRefresh(context.connection), | ||
| onAuthenticationRefreshFailed: context => this.scheduleAuthenticationRefresh(context.connection), | ||
| }); |
| const connection = { | ||
| on: jest.fn(), | ||
| onclose: jest.fn(), | ||
| start: () => Promise.resolve(), | ||
| state: HubConnectionState.Connected, | ||
| } as unknown as HubConnection; |
|
#68676 adds MaximumAuthenticationExpiration which should give the 30 minute behavior IdentityRevalidatingAuthenticationStateProvider used to have. |
e74ed37 to
2b57f7b
Compare
1ac4df6 to
784ca35
Compare
| // security stamp validation interval has elapsed. Update this value if that interval is customized. | ||
| private static readonly TimeSpan s_maximumAuthenticationExpiration = TimeSpan.FromMinutes(40); | ||
|
|
||
| public static void ConfigureIdentityAuthenticationRefresh(this ServerComponentsEndpointOptions options) |
There was a problem hiding this comment.
This only composes if ConfigureConnection was assigned before the call. Assigning it after, which is the natural edit to make in that lambda in Program.cs, silently drops the cap and CloseOnAuthenticationExpiration and auth just quietly goes stale. Is composing worth much if it only covers one of the two orders?
I think we probably shouldn't bother even trying to run any existing options.ConfigureConnection callback. It's templated code, so people are free to modify ConfigureIdentityAuthenticationRefresh themselves if they want to merge their logic with the clamping logic.
784ca35 to
8ac197d
Compare
|
/azp run aspnetcore-ci |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
8ac197d to
5aaa56b
Compare
|
/ba-g looks like an unrelated flaky test |
Summary
This PR is stacked on #68676 and uses its server-enforced SignalR authentication expiration mechanism to replace the Blazor Identity template's
IdentityRevalidatingAuthenticationStateProvider.MaximumAuthenticationExpirationto 40 minutes for generated Individual-auth server appsCloseOnAuthenticationExpiration, so a connection that does not refresh is closed at the enforced deadlineConfigureConnectionconfigurationIdentityRevalidatingAuthenticationStateProviderand its template registrationDoes the 40-minute interval drift?
No.
MaximumAuthenticationExpirationis applied relative to each successful authentication refresh. SignalR refreshes approximately five minutes before that deadline, so the Identity cookie handler reauthenticates at roughly 35 minutes—after its default 30-minute security-stamp validation interval has elapsed.A successful stamp validation renews the principal/cookie, and the next 40-minute maximum is measured from that refresh. Normal timer and request latency can shift an individual refresh slightly, but the offset does not accumulate across successful refresh cycles.
If refresh does not succeed, the connection expiration is not advanced and the server closes the connection at the existing deadline.
Invariants
Follow-up
Identity clock-skew behavior for periodic reauthentication is tracked in #68694.
Validation
CircuitManagerauthentication-refresh configuration testProgram.csshape; local launch is blocked by the unavailable11.0.0-devshared runtime in the template test harness